<script setup>
import { onMounted, ref, watch, getCurrentInstance } from "vue"
import * as TT from "/@/libs/threejs-tools/src"
import * as THREE from "three"
import { ElMessage } from "element-plus"

/**
 * init websocket
 */
const wsRecvMsg = ref("")
;(() => {
  const { proxy } = getCurrentInstance()
  proxy.wsWrapper.getEmitter().on("ws-message", (msg) => {
    console.log("wsEmitter.on:ws-message", msg)
    wsRecvMsg.value = msg
  })
})()

onMounted(() => {
  initThreejs()
})

/**
 * init threejs and load stl model
 */
const container = ref(null)
const containerCut = ref(null)
const cutY = ref(0)

function initThreejs() {
  TT.initDefault(container.value, container, {
    cameraPosition: [0, 0, 500],
    clearColor: "#333",
  })
  // container.grid = TT.addGrid(container, { size: 100 })
  // container.grid.position.y = cutY.value = 50
  // container.mesh = TT.addFaces(container.scene, [0, 100, 0, -50, 50, 0, 50, 0, 0])
  // TT.fitView(container, container.mesh)

  TT.loadStl("/model/Monarch_XI_branded.stl").then((res) => {
    container.mesh = res
    // container.mesh.material.wireframe = true
    container.scene.add(container.mesh)
    const boundingSphere = TT.fitView(container, container.mesh)

    container.grid = TT.addGrid(container, {
      center: boundingSphere.center,
      size: boundingSphere.radius * 2.2,
    })
    container.grid.position.y = cutY.value = boundingSphere.center.y
  })

  TT.initDefault(containerCut.value, containerCut, {
    cameraPosition: [0, 0, 500],
    clearColor: "#333",
  })
}

watch(cutY, (newV) => {
  container.grid.position.y = newV
})

function onCutClick() {
  const slices = cut(+cutY.value)
  if (!slices) {
    return
  }

  containerCut.sliceGroup && containerCut.scene.remove(containerCut.sliceGroup)
  containerCut.sliceGroup = new THREE.Object3D()
  containerCut.scene.add(containerCut.sliceGroup)
  TT.addFaces(containerCut.sliceGroup, slices.uPoints, {
    normals: slices.uNormals,
  }).position.y += 30
  TT.addFaces(containerCut.sliceGroup, slices.dPoints, {
    normals: slices.dNormals,
  })
  TT.fitView(containerCut, containerCut.sliceGroup.children[0])
}

function cut(y) {
  if (!container.mesh) {
    ElMessage.error("no mesh to cut")
    return
  }
  const position = container.mesh.geometry.attributes.position
  const faceSize = 3 * position.itemSize
  const faceCount = position.array.length / faceSize
  const normal = container.mesh.geometry.attributes.normal

  const uPoints = []
  const dPoints = []
  const uNormals = []
  const dNormals = []
  for (let i = 0; i < faceCount; i++) {
    const face = position.array.slice(i * faceSize, (i + 1) * faceSize)
    const faceNormal = normal.array.slice(i * faceSize, (i + 1) * faceSize)
    const { uFaces, dFaces, uFaceNormals, dFaceNormals } = cutFace(
      face,
      y,
      faceNormal
    )
    uPoints.push(...uFaces)
    dPoints.push(...dFaces)
    uNormals.push(...uFaceNormals)
    dNormals.push(...dFaceNormals)
  }
  return { uPoints, dPoints, uNormals, dNormals }
}

function cutFace(face, y, normal) {
  // transform points format
  const points = [0, 1, 2].map((i) => {
    const [x, y, z] = face.slice(i * 3, i * 3 + 3)
    return { x, y, z }
  })
  const pointNormals = [0, 1, 2].map((i) => {
    return [0, 1, 2].map((j) => normal[i * 3 + j])
  })

  // no cut
  const pointYs = points.map((p) => p.y)
  if (Math.max(pointYs) <= y) {
    return { uFaces: [], dFaces: face }
  } else if (Math.min(pointYs) >= y) {
    return { uFaces: face, dFaces: [] }
  }

  // insert cut point
  let newPointNormals = [...pointNormals]
  let newPoints = [...points]
  for (let i = 0, j = 0; i < 3; i++, j++) {
    const start = points[i]
    const end = points[(i + 1) % 3]
    if (Math.max(start.y, end.y) <= y || Math.min(start.y, end.y) >= y) {
      continue
    }
    const ratio = (y - start.y) / (end.y - start.y)
    const curPoint = {
      x: start.x + (end.x - start.x) * ratio,
      y,
      z: start.z + (end.z - start.z) * ratio,
    }
    newPoints.splice(j + 1, 0, curPoint)
    newPointNormals.splice(j + 1, 0, pointNormals[i])
    j++
  }

  // transform points format back
  newPoints = newPoints.map((p) => [p.x, p.y, p.z])

  // calc up and down face
  let uFaces = []
  let dFaces = []
  let uFaceNormals = []
  let dFaceNormals = []
  newPoints.forEach((p, i) => {
    if (p[1] >= y) {
      uFaces.push(p)
      uFaceNormals.push(newPointNormals[i])
    }
    if (p[1] <= y) {
      dFaces.push(p)
      dFaceNormals.push(newPointNormals[i])
    }
  })
  if (uFaces.length === 4) {
    uFaces = [0, 1, 2, 0, 2, 3].map((i) => uFaces[i])
    uFaceNormals = [0, 1, 2, 0, 2, 3].map((i) => uFaceNormals[i])
  }
  if (dFaces.length === 4) {
    dFaces = [0, 1, 2, 0, 2, 3].map((i) => dFaces[i])
    dFaceNormals = [0, 1, 2, 0, 2, 3].map((i) => dFaceNormals[i])
  }

  return { uFaces, dFaces, uFaceNormals, dFaceNormals }
}
</script>

<template>
  <div class="cut-slice">
    <div class="ws-recv-msg">CutSliceDemo's websocket message: {{wsRecvMsg}}</div>
    <!-- <div class="ws-recv-msg">{{newPoints}}</div> -->
    <el-row justify="space-between" style="height: 500px">
      <el-col :span="10">
        <div ref="container" class="container full-w" style="height: 100%;" />
      </el-col>
      <el-col :span="2">
        <el-input v-model="cutY" type="number" class="mt20" />
        <el-button type="primary" class="mt20 full-w" @click="onCutClick">Cut</el-button>
      </el-col>
      <el-col :span="10">
        <div ref="containerCut" class="container full-w" style="height: 100%;" />
      </el-col>
    </el-row>
  </div>
</template>

<style lang="scss" scoped>
.ws-recv-msg {
  background: #444;
  margin-bottom: 20px;
  text-align: start;
  padding: 10px;
}
.container {
  position: relative;
  margin-top: 20px;
}
</style>

